This notebook outlines my process of generating models regarding champion statistics. This notebook is dependent on the data table gameInfo generated from DataExtraction.RMD.

Packages

library(plotly)
library(RColorBrewer)
Warning: package ‘RColorBrewer’ was built under R version 4.1.1
library(data.table)
library(cluster)

Changing Data Structure and Making a List to Store Results

gameInfo.win <- gameInfo %>% 
  mutate(win = as.logical(win)) # Changing to logical to make future code easier
# Commenting out to make sure that I don't run over my cashed results
# winrate <- list()
winrate <- list(
  tables = list(),
  plots = list()
)

Overall Champion Winrate

winrate$tables$base <- gameInfo.win %>% 
  group_by(championName) %>% 
  summarize(winrate = mean(win), games = n(), .groups = "drop")

head(winrate$tables$base)
winrate$plots$base <- winrate$tables$base %>% 
  left_join(
    champions,
    by = c("championName" = "id")
  ) %>% 
  plot_ly(
    x = ~games,
    y = ~winrate,
    color = ~tag1,
    text = ~championName,
    type = "scatter",
    mode = "markers"
  )

winrate$plots$base
# Akshan not present in the champions data gotten FROM the riot games api...RITO PLS. That's the NA in the plot...

Lux is very popular and has a high win rate. Also yeesh, Ryze has a really low win rate in Season 11. Maybe we can see a bit more when we include the tier of play. Weirdly a lot of marksmen in the very popular category. From inspection there might be three groups of champions: 1. Balanced with medium play rate 2. Balanced with high play rate 3. Under tuned with low play rate My worry with a k-means is that the relative importance games played will overshadow the winrate statistic - maybe we can normalize the dataset. # K-means Clustering ## Normalize Data Set and run kmeans models

winrate$tables$base.normal <- winrate$tables$base %>% 
  mutate(winrate = (winrate - mean(winrate))/sd(winrate), games = (games - mean(games))/sd(games))

results$models$winplay$kmeans <- list() # List to store k-means models
results$models$winplay$silhouette <- tibble(k = 2:9, sumofsq = rep(0,8))

set.seed(1)
for(i in 1:8){
  
  results$models$winplay$kmeans[[i]] <- kmeans(winrate$tables$base.normal %>% select(!championName), centers = i + 1, nstart = 50)
  
  results$models$winplay$silhouette$sumofsq[[i]] <- results$models$winplay$kmeans[[i]]$tot.withinss
  
}
rm(i)
results$models$winplay$silhouette %>% 
  plot_ly(
    x = ~k,
    y = ~sumofsq,
    type = "scatter",
    mode = "lines+markers"
  )

It would appear that 3 or 4 clusters is optimal - let’s plot them: ## Plotting ### Setting up Data Table

data.temp$clusterMembership <- tibble(base = rep(0,157))
for(i in 1:length(results$models$winplay$kmeans)){
  
  data.temp$clusterMembership[,i] <- results$models$winplay$kmeans[[i]]$cluster
  
} 
rm(i)
data.temp$names <- str_c(rep("k = "), 2:9)
winrate$tables$clustered <- bind_cols(
    winrate$tables$base,
    data.temp$clusterMembership %>% 
      `names<-`(data.temp$names)
  )

winrate$tables$clustered

Graphing Method 1

# Will use this in the future, even if there is that ugly play button
winrate$tables$clustered %>% 
  pivot_longer(cols = 4:11, names_to = "n_clusters", values_to = "membership") %>%
  plot_ly(
    x = ~games,
    y = ~winrate,
    colors = "Set3",
    color = ~membership,
    frame = ~n_clusters,
    type = "scatter",
    mode = "markers",
    text = ~championName
  ) %>% 
  layout(
    title = "Cluster Membership",
    xaxis = list(title = "Games Played"),
    yaxis = list(title = "Winrate")
  ) %>% 
  animation_opts(
    frame = 100
  )

Graphing Method 2 (Really annoying but better plot)

plotly_args <- list() # List to store plotly arguments
plotly_args$traces <- list() # List of plots 

for(i in 1:length(results$models$winplay$kmeans)){
  
  plotly_args$traces[[i]] <- list(
    visible = F,
    name = i+1,
    x = winrate$tables$clustered$games,
    y = winrate$tables$clustered$winrate,
    text = winrate$tables$clustered$championName,
    color = winrate$tables$clustered[,i+3][[1]],
    colors = "Set3"
  )
  
}

plotly_args$traces[2][[1]]$visible = T # Manually setting the k=3 plot to be visible first

plotly_args$steps <- list() # List to store objects populated by loop
data.temp$fig <- plot_ly()

for(i in 1:length(results$models$winplay$kmeans)){
  
  data.temp$fig <- add_markers(
    data.temp$fig,
    x = plotly_args$traces[i][[1]]$x,
    y = plotly_args$traces[i][[1]]$y,
    color = plotly_args$traces[i][[1]]$color,
    colors = plotly_args$traces[i][[1]]$colors,
    visible = plotly_args$traces[i][[1]]$visible,
    name = plotly_args$traces[i][[1]]$name,
    text = plotly_args$traces[i][[1]]$text,
    type = "scatter",
    mode = "markers",
    hovertext = plotly_args$traces[i][[1]]$text,
    showlegend = F
  )
  
  plotly_args$step <- list(
    args = list(
      "visible",
      rep(F, length(plotly_args$traces))
    ),
    method = "restyle",
    label = plotly_args$traces[i][[1]]$name
  )
  
  plotly_args$step$args[[2]][i] = T
  plotly_args$steps[[i]] = plotly_args$step
  
}
rm(i)

winrate$plots$kmeans <- data.temp$fig %>% 
  layout(
    sliders = list(list(
      active = 1, # 0 indexed in R, nice
      currentvalue = list(prefix = "k = "),
      steps = plotly_args$steps,
      pad = list(t = 45)
    ))
  ) %>% 
  layout(
    title = "Cluster Membership",
    xaxis = list(title = "Games Played"),
    yaxis = list(title = "Winrate")
  )

winrate$plots$kmeans

The 3 cluster model was too general and the 4 cluster model identifies an under performing group but includes champions like Cassiopiea with a decent win rate (0.495) in this group. The 5 cluster model appears to capture this under performing group of champions without gross overestimating. This is presuming that there is indeed an underlying structure to this space which may not be the case. It may be the case that a more complex space may yield more representative results as k-means clustering uses euclidean distance.

It’s also unclear whether or not the low play rate is a cause of the low win rate or because of champion imbalance. Optimal prescriptive balance changes might simply be a simple binary classifier in which champions below a certain win rate need buffs and the converse for high win rate champions.

So low play rate might be caused by a few factors: 1. The champion is weak A. The champion cannot carry even when given gold B. They have a hard time acquiring gold 2.

Adding Tier Information

winrate$tables$tier <- gameInfo.win %>% 
  group_by(championName, tier) %>% 
  summarize(winrate = mean(win), games = n(), .groups = "drop")

head(winrate$tables$tier)

Well we can for sure see regression towards the mean with a higher number of games wit ha few notable exceptions: 1. Rell is popping off in Diamond but likely variance due to low game number. Similar with Ornn in Iron. 2. One notable outlier that can be viewed instantly is Iron Yuumi, with a winrate of 0.434 which matches intuition. 3. Similarly, Ryze, Zoe, and Gwen have horrible win rates in bronze for the number of games. However this also might be due to oversampling of particular players in the scraping process.

A few ADC’s also grab my attention - Ezreal and Lucian seem to have an especially low winrate in Diamond. Might be worth investigating.

Covariate Champion Winrate

Are there any particular combinations of champions which are truly degenerate (see Master Yi - Taric funneling which was a gigantic issue in previous seasons) ## Temporary Table to Make Funciton More Efficient

data.temp$champTeams <- gameInfo.win %>% 
  select(match, win, championName) %>%
  group_by(match, win) %>% 
  mutate(champion = row_number()) %>% 
  pivot_wider(
    names_from = champion,
    values_from = championName
  ) %>% 
  mutate(team = str_c("_",`2`,`3`,`4`,`5`,"_", sep = "_")) %>% 
  select(match, win, champion, team) %>% 
  ungroup()
Error: Can't subset columns that don't exist.
x Column `champion` doesn't exist.
Run `rlang::last_error()` to see where the error occurred.

Executing Function

winrate$plots$covariate
Warning: Ignoring 2578 observations
Warning: Ignoring 2578 observations

Probably not enough games to make any predictions - with 155*154 champion duo combinations, would need a lot more data to have a decent sample of all combinations.

LS0tDQp0aXRsZTogIkNoYW1waW9uIFN0YXRpc3RpY3MiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGlzIG5vdGVib29rIG91dGxpbmVzIG15IHByb2Nlc3Mgb2YgZ2VuZXJhdGluZyBtb2RlbHMgcmVnYXJkaW5nIGNoYW1waW9uIHN0YXRpc3RpY3MuIFRoaXMgbm90ZWJvb2sgaXMgZGVwZW5kZW50IG9uIHRoZSBkYXRhIHRhYmxlIGdhbWVJbmZvIGdlbmVyYXRlZCBmcm9tIERhdGFFeHRyYWN0aW9uLlJNRC4NCg0KIyBQYWNrYWdlcw0KYGBge3J9DQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShjbHVzdGVyKQ0KYGBgDQoNCg0KIyBDaGFuZ2luZyBEYXRhIFN0cnVjdHVyZSBhbmQgTWFraW5nIGEgTGlzdCB0byBTdG9yZSBSZXN1bHRzDQpgYGB7cn0NCmdhbWVJbmZvLndpbiA8LSBnYW1lSW5mbyAlPiUgDQogIG11dGF0ZSh3aW4gPSBhcy5sb2dpY2FsKHdpbikpICMgQ2hhbmdpbmcgdG8gbG9naWNhbCB0byBtYWtlIGZ1dHVyZSBjb2RlIGVhc2llcg0KIyAjIENvbW1lbnRpbmcgb3V0IHRvIG1ha2Ugc3VyZSB0aGF0IEkgZG9uJ3QgcnVuIG92ZXIgbXkgY2FzaGVkIHJlc3VsdHMNCiMgd2lucmF0ZSA8LSBsaXN0KA0KIyAgIHRhYmxlcyA9IGxpc3QoKSwNCiMgICBwbG90cyA9IGxpc3QoKQ0KIyApDQpgYGANCg0KIyBPdmVyYWxsIENoYW1waW9uIFdpbnJhdGUNCmBgYHtyfQ0Kd2lucmF0ZSR0YWJsZXMkYmFzZSA8LSBnYW1lSW5mby53aW4gJT4lIA0KICBncm91cF9ieShjaGFtcGlvbk5hbWUpICU+JSANCiAgc3VtbWFyaXplKHdpbnJhdGUgPSBtZWFuKHdpbiksIGdhbWVzID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKQ0KDQpoZWFkKHdpbnJhdGUkdGFibGVzJGJhc2UpDQpgYGANCmBgYHtyfQ0Kd2lucmF0ZSRwbG90cyRiYXNlIDwtIHdpbnJhdGUkdGFibGVzJGJhc2UgJT4lIA0KICBsZWZ0X2pvaW4oDQogICAgY2hhbXBpb25zLA0KICAgIGJ5ID0gYygiY2hhbXBpb25OYW1lIiA9ICJpZCIpDQogICkgJT4lIA0KICBwbG90X2x5KA0KICAgIHggPSB+Z2FtZXMsDQogICAgeSA9IH53aW5yYXRlLA0KICAgIGNvbG9yID0gfnRhZzEsDQogICAgdGV4dCA9IH5jaGFtcGlvbk5hbWUsDQogICAgdHlwZSA9ICJzY2F0dGVyIiwNCiAgICBtb2RlID0gIm1hcmtlcnMiDQogICkNCg0Kd2lucmF0ZSRwbG90cyRiYXNlDQojIEFrc2hhbiBub3QgcHJlc2VudCBpbiB0aGUgY2hhbXBpb25zIGRhdGEgZ290dGVuIEZST00gdGhlIHJpb3QgZ2FtZXMgYXBpLi4uUklUTyBQTFMuIFRoYXQncyB0aGUgTkEgaW4gdGhlIHBsb3QuLi4NCmBgYA0KTHV4IGlzIHZlcnkgcG9wdWxhciBhbmQgaGFzIGEgaGlnaCB3aW4gcmF0ZS4gQWxzbyB5ZWVzaCwgUnl6ZSBoYXMgYSByZWFsbHkgbG93IHdpbiByYXRlIGluIFNlYXNvbiAxMS4gTWF5YmUgd2UgY2FuIHNlZSBhIGJpdCBtb3JlIHdoZW4gd2UgaW5jbHVkZSB0aGUgdGllciBvZiBwbGF5LiBXZWlyZGx5IGEgbG90IG9mIG1hcmtzbWVuIGluIHRoZSB2ZXJ5IHBvcHVsYXIgY2F0ZWdvcnkuIEZyb20gaW5zcGVjdGlvbiB0aGVyZSBtaWdodCBiZSB0aHJlZSBncm91cHMgb2YgY2hhbXBpb25zOg0KMS4gQmFsYW5jZWQgd2l0aCBtZWRpdW0gcGxheSByYXRlDQoyLiBCYWxhbmNlZCB3aXRoIGhpZ2ggcGxheSByYXRlDQozLiBVbmRlciB0dW5lZCB3aXRoIGxvdyBwbGF5IHJhdGUNCk15IHdvcnJ5IHdpdGggYSBrLW1lYW5zIGlzIHRoYXQgdGhlIHJlbGF0aXZlIGltcG9ydGFuY2UgZ2FtZXMgcGxheWVkIHdpbGwgb3ZlcnNoYWRvdyB0aGUgd2lucmF0ZSBzdGF0aXN0aWMgLSBtYXliZSB3ZSBjYW4gbm9ybWFsaXplIHRoZSBkYXRhc2V0Lg0KIyBLLW1lYW5zIENsdXN0ZXJpbmcNCiMjIE5vcm1hbGl6ZSBEYXRhIFNldCBhbmQgcnVuIGttZWFucyBtb2RlbHMNCmBgYHtyfQ0Kd2lucmF0ZSR0YWJsZXMkYmFzZS5ub3JtYWwgPC0gd2lucmF0ZSR0YWJsZXMkYmFzZSAlPiUgDQogIG11dGF0ZSh3aW5yYXRlID0gKHdpbnJhdGUgLSBtZWFuKHdpbnJhdGUpKS9zZCh3aW5yYXRlKSwgZ2FtZXMgPSAoZ2FtZXMgLSBtZWFuKGdhbWVzKSkvc2QoZ2FtZXMpKQ0KDQpyZXN1bHRzJG1vZGVscyR3aW5wbGF5JGttZWFucyA8LSBsaXN0KCkgIyBMaXN0IHRvIHN0b3JlIGstbWVhbnMgbW9kZWxzDQpyZXN1bHRzJG1vZGVscyR3aW5wbGF5JHNpbGhvdWV0dGUgPC0gdGliYmxlKGsgPSAyOjksIHN1bW9mc3EgPSByZXAoMCw4KSkNCg0Kc2V0LnNlZWQoMSkNCmZvcihpIGluIDE6OCl7DQogIA0KICByZXN1bHRzJG1vZGVscyR3aW5wbGF5JGttZWFuc1tbaV1dIDwtIGttZWFucyh3aW5yYXRlJHRhYmxlcyRiYXNlLm5vcm1hbCAlPiUgc2VsZWN0KCFjaGFtcGlvbk5hbWUpLCBjZW50ZXJzID0gaSArIDEsIG5zdGFydCA9IDUwKQ0KICANCiAgcmVzdWx0cyRtb2RlbHMkd2lucGxheSRzaWxob3VldHRlJHN1bW9mc3FbW2ldXSA8LSByZXN1bHRzJG1vZGVscyR3aW5wbGF5JGttZWFuc1tbaV1dJHRvdC53aXRoaW5zcw0KICANCn0NCnJtKGkpDQpyZXN1bHRzJG1vZGVscyR3aW5wbGF5JHNpbGhvdWV0dGUgJT4lIA0KICBwbG90X2x5KA0KICAgIHggPSB+aywNCiAgICB5ID0gfnN1bW9mc3EsDQogICAgdHlwZSA9ICJzY2F0dGVyIiwNCiAgICBtb2RlID0gImxpbmVzK21hcmtlcnMiDQogICkNCmBgYA0KSXQgd291bGQgYXBwZWFyIHRoYXQgMyBvciA0IGNsdXN0ZXJzIGlzIG9wdGltYWwgLSBsZXQncyBwbG90IHRoZW06DQojIyBQbG90dGluZw0KIyMjIFNldHRpbmcgdXAgRGF0YSBUYWJsZQ0KYGBge3J9DQpkYXRhLnRlbXAkY2x1c3Rlck1lbWJlcnNoaXAgPC0gdGliYmxlKGJhc2UgPSByZXAoMCwxNTcpKQ0KZm9yKGkgaW4gMTpsZW5ndGgocmVzdWx0cyRtb2RlbHMkd2lucGxheSRrbWVhbnMpKXsNCiAgDQogIGRhdGEudGVtcCRjbHVzdGVyTWVtYmVyc2hpcFssaV0gPC0gcmVzdWx0cyRtb2RlbHMkd2lucGxheSRrbWVhbnNbW2ldXSRjbHVzdGVyDQogIA0KfSANCnJtKGkpDQpkYXRhLnRlbXAkbmFtZXMgPC0gc3RyX2MocmVwKCJrID0gIiksIDI6OSkNCndpbnJhdGUkdGFibGVzJGNsdXN0ZXJlZCA8LSBiaW5kX2NvbHMoDQogICAgd2lucmF0ZSR0YWJsZXMkYmFzZSwNCiAgICBkYXRhLnRlbXAkY2x1c3Rlck1lbWJlcnNoaXAgJT4lIA0KICAgICAgYG5hbWVzPC1gKGRhdGEudGVtcCRuYW1lcykNCiAgKQ0KDQp3aW5yYXRlJHRhYmxlcyRjbHVzdGVyZWQNCmBgYA0KIyMjIEdyYXBoaW5nIE1ldGhvZCAxDQpgYGB7cn0NCiMgV2lsbCB1c2UgdGhpcyBpbiB0aGUgZnV0dXJlLCBldmVuIGlmIHRoZXJlIGlzIHRoYXQgdWdseSBwbGF5IGJ1dHRvbg0Kd2lucmF0ZSR0YWJsZXMkY2x1c3RlcmVkICU+JSANCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSA0OjExLCBuYW1lc190byA9ICJuX2NsdXN0ZXJzIiwgdmFsdWVzX3RvID0gIm1lbWJlcnNoaXAiKSAlPiUNCiAgcGxvdF9seSgNCiAgICB4ID0gfmdhbWVzLA0KICAgIHkgPSB+d2lucmF0ZSwNCiAgICBjb2xvcnMgPSAiU2V0MyIsDQogICAgY29sb3IgPSB+bWVtYmVyc2hpcCwNCiAgICBmcmFtZSA9IH5uX2NsdXN0ZXJzLA0KICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgbW9kZSA9ICJtYXJrZXJzIiwNCiAgICB0ZXh0ID0gfmNoYW1waW9uTmFtZQ0KICApICU+JSANCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gIkNsdXN0ZXIgTWVtYmVyc2hpcCIsDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIkdhbWVzIFBsYXllZCIpLA0KICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJXaW5yYXRlIikNCiAgKSAlPiUgDQogIGFuaW1hdGlvbl9vcHRzKA0KICAgIGZyYW1lID0gMTAwDQogICkNCmBgYA0KDQojIyMgR3JhcGhpbmcgTWV0aG9kIDIgKFJlYWxseSBhbm5veWluZyBidXQgYmV0dGVyIHBsb3QpDQpgYGB7cn0NCnBsb3RseV9hcmdzIDwtIGxpc3QoKSAjIExpc3QgdG8gc3RvcmUgcGxvdGx5IGFyZ3VtZW50cw0KcGxvdGx5X2FyZ3MkdHJhY2VzIDwtIGxpc3QoKSAjIExpc3Qgb2YgcGxvdHMgDQoNCmZvcihpIGluIDE6bGVuZ3RoKHJlc3VsdHMkbW9kZWxzJHdpbnBsYXkka21lYW5zKSl7DQogIA0KICBwbG90bHlfYXJncyR0cmFjZXNbW2ldXSA8LSBsaXN0KA0KICAgIHZpc2libGUgPSBGLA0KICAgIG5hbWUgPSBpKzEsDQogICAgeCA9IHdpbnJhdGUkdGFibGVzJGNsdXN0ZXJlZCRnYW1lcywNCiAgICB5ID0gd2lucmF0ZSR0YWJsZXMkY2x1c3RlcmVkJHdpbnJhdGUsDQogICAgdGV4dCA9IHdpbnJhdGUkdGFibGVzJGNsdXN0ZXJlZCRjaGFtcGlvbk5hbWUsDQogICAgY29sb3IgPSB3aW5yYXRlJHRhYmxlcyRjbHVzdGVyZWRbLGkrM11bWzFdXSwNCiAgICBjb2xvcnMgPSAiU2V0MyINCiAgKQ0KICANCn0NCg0KcGxvdGx5X2FyZ3MkdHJhY2VzWzJdW1sxXV0kdmlzaWJsZSA9IFQgIyBNYW51YWxseSBzZXR0aW5nIHRoZSBrPTMgcGxvdCB0byBiZSB2aXNpYmxlIGZpcnN0DQoNCnBsb3RseV9hcmdzJHN0ZXBzIDwtIGxpc3QoKSAjIExpc3QgdG8gc3RvcmUgb2JqZWN0cyBwb3B1bGF0ZWQgYnkgbG9vcA0KZGF0YS50ZW1wJGZpZyA8LSBwbG90X2x5KCkNCg0KZm9yKGkgaW4gMTpsZW5ndGgocmVzdWx0cyRtb2RlbHMkd2lucGxheSRrbWVhbnMpKXsNCiAgDQogIGRhdGEudGVtcCRmaWcgPC0gYWRkX21hcmtlcnMoDQogICAgZGF0YS50ZW1wJGZpZywNCiAgICB4ID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0keCwNCiAgICB5ID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0keSwNCiAgICBjb2xvciA9IHBsb3RseV9hcmdzJHRyYWNlc1tpXVtbMV1dJGNvbG9yLA0KICAgIGNvbG9ycyA9IHBsb3RseV9hcmdzJHRyYWNlc1tpXVtbMV1dJGNvbG9ycywNCiAgICB2aXNpYmxlID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0kdmlzaWJsZSwNCiAgICBuYW1lID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0kbmFtZSwNCiAgICB0ZXh0ID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0kdGV4dCwNCiAgICB0eXBlID0gInNjYXR0ZXIiLA0KICAgIG1vZGUgPSAibWFya2VycyIsDQogICAgc2hvd2xlZ2VuZCA9IEYNCiAgKQ0KICANCiAgcGxvdGx5X2FyZ3Mkc3RlcCA8LSBsaXN0KA0KICAgIGFyZ3MgPSBsaXN0KA0KICAgICAgInZpc2libGUiLA0KICAgICAgcmVwKEYsIGxlbmd0aChwbG90bHlfYXJncyR0cmFjZXMpKQ0KICAgICksDQogICAgbWV0aG9kID0gInJlc3R5bGUiLA0KICAgIGxhYmVsID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0kbmFtZQ0KICApDQogIA0KICBwbG90bHlfYXJncyRzdGVwJGFyZ3NbWzJdXVtpXSA9IFQNCiAgcGxvdGx5X2FyZ3Mkc3RlcHNbW2ldXSA9IHBsb3RseV9hcmdzJHN0ZXANCiAgDQp9DQpybShpKQ0KDQp3aW5yYXRlJHBsb3RzJGttZWFucyA8LSBkYXRhLnRlbXAkZmlnICU+JSANCiAgbGF5b3V0KA0KICAgIHNsaWRlcnMgPSBsaXN0KGxpc3QoDQogICAgICBhY3RpdmUgPSAxLCAjIDAgaW5kZXhlZCBpbiBSLCBuaWNlDQogICAgICBjdXJyZW50dmFsdWUgPSBsaXN0KHByZWZpeCA9ICJrID0gIiksDQogICAgICBzdGVwcyA9IHBsb3RseV9hcmdzJHN0ZXBzLA0KICAgICAgcGFkID0gbGlzdCh0ID0gNDUpDQogICAgKSkNCiAgKSAlPiUgDQogIGxheW91dCgNCiAgICB0aXRsZSA9ICJDbHVzdGVyIE1lbWJlcnNoaXAiLA0KICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJHYW1lcyBQbGF5ZWQiKSwNCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiV2lucmF0ZSIpDQogICkNCg0Kd2lucmF0ZSRwbG90cyRrbWVhbnMNCmBgYA0KVGhlIDMgY2x1c3RlciBtb2RlbCB3YXMgdG9vIGdlbmVyYWwgYW5kIHRoZSA0IGNsdXN0ZXIgbW9kZWwgaWRlbnRpZmllcyBhbiB1bmRlciBwZXJmb3JtaW5nIGdyb3VwIGJ1dCBpbmNsdWRlcyBjaGFtcGlvbnMgbGlrZSBDYXNzaW9waWVhIHdpdGggYSBkZWNlbnQgd2luIHJhdGUgKDAuNDk1KSBpbiB0aGlzIGdyb3VwLiBUaGUgNSBjbHVzdGVyIG1vZGVsIGFwcGVhcnMgdG8gY2FwdHVyZSB0aGlzIHVuZGVyIHBlcmZvcm1pbmcgZ3JvdXAgb2YgY2hhbXBpb25zIHdpdGhvdXQgZ3Jvc3Mgb3ZlcmVzdGltYXRpbmcuIFRoaXMgaXMgcHJlc3VtaW5nIHRoYXQgdGhlcmUgKmlzKiBpbmRlZWQgYW4gdW5kZXJseWluZyBzdHJ1Y3R1cmUgdG8gdGhpcyBzcGFjZSB3aGljaCBtYXkgbm90IGJlIHRoZSBjYXNlLiBJdCBtYXkgYmUgdGhlIGNhc2UgdGhhdCBhIG1vcmUgY29tcGxleCBzcGFjZSBtYXkgeWllbGQgbW9yZSByZXByZXNlbnRhdGl2ZSByZXN1bHRzIGFzIGstbWVhbnMgY2x1c3RlcmluZyB1c2VzIGV1Y2xpZGVhbiBkaXN0YW5jZS4gDQoNCkl0J3MgYWxzbyB1bmNsZWFyIHdoZXRoZXIgb3Igbm90IHRoZSBsb3cgcGxheSByYXRlIGlzIGEgY2F1c2Ugb2YgdGhlIGxvdyB3aW4gcmF0ZSBvciBiZWNhdXNlIG9mIGNoYW1waW9uIGltYmFsYW5jZS4gT3B0aW1hbCBwcmVzY3JpcHRpdmUgYmFsYW5jZSBjaGFuZ2VzIG1pZ2h0IHNpbXBseSBiZSBhIHNpbXBsZSBiaW5hcnkgY2xhc3NpZmllciBpbiB3aGljaCBjaGFtcGlvbnMgYmVsb3cgYSBjZXJ0YWluIHdpbiByYXRlIG5lZWQgYnVmZnMgYW5kIHRoZSBjb252ZXJzZSBmb3IgaGlnaCB3aW4gcmF0ZSBjaGFtcGlvbnMuDQoNClNvIGxvdyBwbGF5IHJhdGUgbWlnaHQgYmUgY2F1c2VkIGJ5IGEgZmV3IGZhY3RvcnM6DQoxLiBUaGUgY2hhbXBpb24gaXMgd2Vhaw0KICBBLiBUaGUgY2hhbXBpb24gY2Fubm90IGNhcnJ5IGV2ZW4gd2hlbiBnaXZlbiBnb2xkDQogIEIuIFRoZXkgaGF2ZSBhIGhhcmQgdGltZSBhY3F1aXJpbmcgZ29sZA0KMi4gDQoNCiMgQWRkaW5nIFRpZXIgSW5mb3JtYXRpb24NCmBgYHtyfQ0Kd2lucmF0ZSR0YWJsZXMkdGllciA8LSBnYW1lSW5mby53aW4gJT4lIA0KICBncm91cF9ieShjaGFtcGlvbk5hbWUsIHRpZXIpICU+JSANCiAgc3VtbWFyaXplKHdpbnJhdGUgPSBtZWFuKHdpbiksIGdhbWVzID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKQ0KDQpoZWFkKHdpbnJhdGUkdGFibGVzJHRpZXIpDQpgYGANCmBgYHtyfQ0Kd2lucmF0ZSRwbG90cyR0aWVyIDwtIHdpbnJhdGUkdGFibGUkdGllciAlPiUgDQogIHBsb3RfbHkoDQogICAgeCA9IH5nYW1lcywNCiAgICB5ID0gfndpbnJhdGUsDQogICAgY29sb3IgPSB+dGllciwNCiAgICB0ZXh0ID0gfmNoYW1waW9uTmFtZSwNCiAgICB0eXBlID0gInNjYXR0ZXIiLA0KICAgIG1vZGUgPSAibWFya2VycyINCiAgKQ0KDQp3aW5yYXRlJHBsb3RzJHRpZXINCmBgYA0KV2VsbCB3ZSBjYW4gZm9yIHN1cmUgc2VlIHJlZ3Jlc3Npb24gdG93YXJkcyB0aGUgbWVhbiB3aXRoIGEgaGlnaGVyIG51bWJlciBvZiBnYW1lcyB3aXQgaGEgZmV3IG5vdGFibGUgZXhjZXB0aW9uczoNCjEuIFJlbGwgaXMgcG9wcGluZyBvZmYgaW4gRGlhbW9uZCBidXQgbGlrZWx5IHZhcmlhbmNlIGR1ZSB0byBsb3cgZ2FtZSBudW1iZXIuIFNpbWlsYXIgd2l0aCBPcm5uIGluIElyb24uIA0KMi4gT25lIG5vdGFibGUgb3V0bGllciB0aGF0IGNhbiBiZSB2aWV3ZWQgaW5zdGFudGx5IGlzIElyb24gWXV1bWksIHdpdGggYSB3aW5yYXRlIG9mIDAuNDM0IHdoaWNoIG1hdGNoZXMgaW50dWl0aW9uLiANCjMuIFNpbWlsYXJseSwgUnl6ZSwgWm9lLCBhbmQgR3dlbiBoYXZlIGhvcnJpYmxlIHdpbiByYXRlcyBpbiBicm9uemUgZm9yIHRoZSBudW1iZXIgb2YgZ2FtZXMuIEhvd2V2ZXIgdGhpcyBhbHNvIG1pZ2h0IGJlIGR1ZSB0byBvdmVyc2FtcGxpbmcgb2YgcGFydGljdWxhciBwbGF5ZXJzIGluIHRoZSBzY3JhcGluZyBwcm9jZXNzLiANCg0KQSBmZXcgQURDJ3MgYWxzbyBncmFiIG15IGF0dGVudGlvbiAtIEV6cmVhbCBhbmQgTHVjaWFuIHNlZW0gdG8gaGF2ZSBhbiBlc3BlY2lhbGx5IGxvdyB3aW5yYXRlIGluIERpYW1vbmQuIE1pZ2h0IGJlIHdvcnRoIGludmVzdGlnYXRpbmcuDQoNCiMgQ292YXJpYXRlIENoYW1waW9uIFdpbnJhdGUNCkFyZSB0aGVyZSBhbnkgcGFydGljdWxhciBjb21iaW5hdGlvbnMgb2YgY2hhbXBpb25zIHdoaWNoIGFyZSB0cnVseSBkZWdlbmVyYXRlIChzZWUgTWFzdGVyIFlpIC0gVGFyaWMgZnVubmVsaW5nIHdoaWNoIHdhcyBhIGdpZ2FudGljIGlzc3VlIGluIHByZXZpb3VzIHNlYXNvbnMpDQojIyBUZW1wb3JhcnkgVGFibGUgdG8gTWFrZSBGdW5jaXRvbiBNb3JlIEVmZmljaWVudA0KYGBge3J9DQpkYXRhLnRlbXAkY2hhbXBUZWFtcyA8LSBnYW1lSW5mby53aW4gJT4lIA0KICBzZWxlY3QobWF0Y2gsIHdpbiwgY2hhbXBpb25OYW1lKSAlPiUNCiAgZ3JvdXBfYnkobWF0Y2gsIHdpbikgJT4lIA0KICBtdXRhdGUoY2hhbXBpb24gPSByb3dfbnVtYmVyKCkpICU+JSANCiAgcGl2b3Rfd2lkZXIoDQogICAgbmFtZXNfZnJvbSA9IGNoYW1waW9uLA0KICAgIHZhbHVlc19mcm9tID0gY2hhbXBpb25OYW1lDQogICkgJT4lIA0KICBtdXRhdGUodGVhbSA9IHN0cl9jKCJfIixgMmAsYDNgLGA0YCxgNWAsIl8iLCBzZXAgPSAiXyIpKSAlPiUgDQogIHNlbGVjdChtYXRjaCwgd2luLCBjaGFtcGlvbiA9IGAxYCwgdGVhbSkgJT4lIA0KICB1bmdyb3VwKCkNCmBgYA0KIyMgRXhlY3V0aW5nIEZ1bmN0aW9uDQpgYGB7cn0NCndpbnJhdGUkdGFibGVzJGNvdmFyaWF0ZS50ZW1wIDwtIGV4cGFuZC5ncmlkKA0KICBjaGFtcGlvbnMkaWQsDQogIGNoYW1waW9ucyRpZA0KKSAlPiUNCiAgYXBwbHkoLiwgMSwgc29ydCkgJT4lICMgUmVtb3Zpbmcgcm93cyBzeW1tZXRyaWNhbGx5LCBubyBuZWVkIHRvIGRvdWJsZSBzZWFyY2gNCiAgdCgpICU+JSANCiAgdW5pcXVlKCkgJT4lIA0KICBhc190aWJibGUoKSAlPiUgDQogIHJlbmFtZShDaGFtcDEgPSBWMSwgQ2hhbXAyID0gVjIpICU+JSANCiAgZmlsdGVyKENoYW1wMSAhPSBDaGFtcDIpICMgUmVtb3ZpbmcgZGlhZ29uYWwgZWxlbWVudHMNCg0KIyBUaGlzIHRha2VzIFNPIGRhbW4gbG9uZyB0byBydW4sIEkgd29uZGVyIGlmIHRoZXJlIGlzIGEgYmV0dGVyIHdheSB0byBkbyB0aGlzLi4uDQojIERvaW5nIHRoaXMgaW4gdHdvIHN0ZXBzIHRvIGF2b2lkIHJ1bm5pbmcgbG9uZyBmdW5jdGlvbnMgYWdhaW4NCiMgd2lucmF0ZSR0YWJsZXMkY292YXJpYXRlLnRlbXAyIDwtIHdpbnJhdGUkdGFibGVzJGNvdmFyaWF0ZS50ZW1wICU+JQ0KIyAgIG11dGF0ZSgNCiMgICAgIGdhbWVMaXN0ID0gcG1hcCgNCiMgICAgICAgLiwNCiMgICAgICAgLmYgPSBmdW5jdGlvbihDaGFtcDEsIENoYW1wMiwgVEFCTEUgPSBkYXRhLnRlbXAkY2hhbXBUZWFtcyl7DQojIA0KIyAgICAgICAgIFRBQkxFICU+JQ0KIyAgICAgICAgICAgZ3JvdXBfYnkobWF0Y2gsIHdpbikgJT4lDQojICAgICAgICAgICBmaWx0ZXIoY2hhbXBpb24gPT0gQ2hhbXAxKSAlPiUgIyBPa2F5IHRoaXMgaXMgd2F5IGZhc3RlciBidXQgc3RpbGwgdHVyYm8gc2xvdw0KIyAgICAgICAgICAgZmlsdGVyKHN0cl9kZXRlY3QodGVhbSwgcGFzdGUwKCJfIiwgQ2hhbXAyLCAiXyIpKSkgJT4lIA0KIyAgICAgICAgICAgdW5ncm91cCgpICU+JQ0KIyAgICAgICAgICAgcmV0dXJuKCkNCiMgDQojICAgICAgIH0NCiMgICAgICkNCiMgICApDQoNCndpbnJhdGUkdGFibGVzJGNvdmFyaWF0ZSA8LSB3aW5yYXRlJHRhYmxlcyRjb3ZhcmlhdGUudGVtcDIgJT4lIA0KICBtdXRhdGUoDQogICAgZGF0YSA9IG1hcCgNCiAgICAgIGdhbWVMaXN0LA0KICAgICAgLmYgPSBmdW5jdGlvbihnYW1lTGlzdCl7DQogICAgICAgIA0KICAgICAgICBnYW1lTGlzdCAlPiUgDQogICAgICAgICAgc3VtbWFyaXplKHdpbnJhdGUgPSBtZWFuKHdpbiksIGdhbWVzID0gbigpKSAlPiUgDQogICAgICAgICAgcmV0dXJuKCkNCiAgICAgICAgDQogICAgICB9DQogICAgKQ0KICApICU+JSANCiAgdW5uZXN0KGNvbHMgPSBjKGRhdGEpKQ0KDQojIE1ha2luZyBhIG5ldyB0YWJsZSB0byBtYWtlIHNlYXJjaGluZyBwYXJ0aWN1bGFyIGNoYW1waW9uIHBhaXJzIGVhc2llcg0Kd2lucmF0ZSR0YWJsZXMkY292YXJpYXRlLnNlYXJjaCA8LSBiaW5kX3Jvd3MoDQogIHdpbnJhdGUkdGFibGVzJGNvdmFyaWF0ZSwNCiAgd2lucmF0ZSR0YWJsZXMkY292YXJpYXRlICU+JSANCiAgICByZW5hbWUoQ2hhbXAyID0gMSwgQ2hhbXAxID0gMikNCikNCg0KDQp3aW5yYXRlJHBsb3RzJGNvdmFyaWF0ZSA8LSB3aW5yYXRlJHRhYmxlcyRjb3ZhcmlhdGUgJT4lIA0KICBwbG90X2x5KA0KICAgIHggPSB+Z2FtZXMsDQogICAgeSA9IH53aW5yYXRlLA0KICAgIHRleHQgPSB+cGFzdGUoQ2hhbXAxLCBDaGFtcDIsIHNlcCA9ICIgIiksDQogICAgdHlwZSA9ICJzY2F0dGVyIiwNCiAgICBtb2RlID0gIm1hcmtlcnMiDQogICkNCg0Kd2lucmF0ZSRwbG90cyRjb3ZhcmlhdGUgDQpgYGANClByb2JhYmx5IG5vdCBlbm91Z2ggZ2FtZXMgdG8gbWFrZSBhbnkgcHJlZGljdGlvbnMgLSB3aXRoIDE1NSoxNTQgY2hhbXBpb24gZHVvIGNvbWJpbmF0aW9ucywgd291bGQgbmVlZCBhIGxvdCBtb3JlIGRhdGEgdG8gaGF2ZSBhIGRlY2VudCBzYW1wbGUgb2YgYWxsIGNvbWJpbmF0aW9ucy4=